﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;

namespace StuffNET.Other
{
    public static class DynamicCompiler
    {
        /// <summary>
        /// Executes the passed code on the fly
        /// </summary>
        /// <param name="code">The code that should be executed</param>
        /// <param name="libraries">Paths to libraries the passed code uses</param>
        /// <param name="usings">Usings the code needs</param>
        /// <param name="compilerVersion">The version of the used compiler. Default version is 3.5</param>
        /// <returns>The result of the executed code. Any return statements inside the code will set the result object. If the code does not contain a return statement, result will be null</returns>
        public static object Execute(string code, string[] libraries, string[] usings, string compilerVersion = "v3.5")
        {
            CSharpCodeProvider cscp = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", compilerVersion } });
            CompilerParameters cp = new CompilerParameters(libraries);
            cp.GenerateExecutable = false;
            cp.GenerateInMemory = true;
            cp.IncludeDebugInformation = false;
            cp.OutputAssembly = "TempAssembly";
            CompilerResults cr = cscp.CompileAssemblyFromSource(cp, BuildMethod(code, usings));
            if (cr.Errors.HasErrors)
            {
                string errorString = "";
                foreach (CompilerError error in cr.Errors)
                {
                    errorString += "- " + error.ErrorText + "\n\r";
                }
                throw new ArgumentException("The code is invalid:\n\r" + errorString);
            }
            MethodInfo mi = cr.CompiledAssembly.GetType("TempProj.TempClass").GetMethod("TempMethod", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            object result = null;
            result = mi.Invoke(null, null);
            return result;
        }

        /// <summary>
        /// Executes the passed code on the fly
        /// </summary>
        /// <param name="code">The code that should be executed</param>
        /// <returns>The result of the executed code. Any return statements inside the code will set the result object. If the code does not contain a return statement, result will be null</returns>
        public static object Execute(string code)
        {
            return Execute(code, new string[] { }, new string[] { "System" });
        }
        
        private static string BuildMethod(string code, string[] usings)
        {
            string fullCode = "";
            if (usings != null)
            {
                foreach (string s in usings)
                {
                    fullCode += "using " + s + ";\n\r";
                }
            }
            fullCode += "\n\rnamespace TempProj\n\r{\n\rpublic static class TempClass\n\r{\n\rpublic static object TempMethod()\n\r{\n\r";
            if (!code.Contains("return"))
                code += "return null;\n\r";
            fullCode += code;
            fullCode += "\n\r}\n\r}\n\r}";
            return fullCode;
        }
    }
}
